;*****************************************************************************************************
;+
; NAME:
;       IGL_BASERANGEOBJECT
;
; PURPOSE:
;
;       This is an abstract class to represent graphic objects which can be panned
;       and/or zoomed. It is written to easily take care of
;       pan/zoom operations which are common to most objects in the
;       Interactive Graphics Library.
;
;
; AUTHORS:
;
;        RAPHAEL SADOUN
;        Graduate Student
;        Institut d'Astrophysique de Paris
;        98 bis boulevard Arago
;        75014 PARIS FRANCE
;        E-mail: raphael.sadoun@gmail.com
;
; SUPERCLASSES:
;
;       CATDATAATOM (Catalyst Library)
;
; SYNTAX:
;
;       object = Obj_New('IGL_BaseRangeObject')
;
;
; CLASS_STRUCTURE:
;
;      class = {IGL_BaseRangeObject, $
;               INHERITS CATDATAATOM $ ; This is a CATDATAATOM object
;              }
;
;
;
; MODIFICATION_HISTORY:
;
;       Written by: Raphael Sadoun, 24 April 2012
;-
;*****************************************************************************************************
;+
; NAME:
;       IGL_BaseRangeObject::CONTROLPANEL
;
; PURPOSE:
;
;       This method creates a control panel for the IGL_BaseRangeObject
;       object. 
;
; SYNTAX:
;
;       self -> ControlPanel, baseObject
;
; ARGUMENTS:
;
;       baseObject:    The object reference of a base widget for this control to
;                                be added to. If not supplied, the control panel will be in a
;                                self contained window (i.e., a TOPLEVELBASE object).
;
; KEYWORDS:
;
;       _EXTRA:       Any keywords appropriate for the CatControlPanel::INIT method.
;
;-
;*****************************************************************************************************
PRO IGL_BaseRangeObject::ControlPanel, baseObject, _EXTRA=extraKeywords

   @cat_pro_error_handler

   ;; Create a new control panel
   cp = OBJ_NEW ('CatControlPanel', self, PARENT=baseObject, COLUMN=1, $
      TITLE='IGL_BaseRangeObject Control Panel', _EXTRA=extraKeywords, /No_Cancel, /No_Apply, /No_OK)

   IF (NOT OBJ_VALID (cp)) THEN RETURN

   aproperties = Obj_New('PROPERTYSHEETWIDGET', cp, Value=self, Name='IGL_BASERANGEOBJECT PROPERTYSHEET', YSize=10)
   aproperties -> SetProperty, Event_Object=self

   ;; Display the control panel if it created its own TLB.
   IF cp -> Created_Own_TLB(tlb) THEN tlb -> Draw, /Center

   ;; Report completion
   self -> Report, /Completed

END

;*****************************************************************************************************
;+
; NAME:
;        IGL_BaseRangeObject::EVENT_HANDLER
;
; PURPOSE:
;
;        This method is the event handler for the IGL_BaseRangeObject
;        object. It responds to events generated by the property sheet.
;
; SYNTAX:
;
;        This method is called automatically by the event handling mechanism.
;
; ARGUMENTS:
;
;       event: The event structure as described in the IDL help files, except
;              that the ID, TOP and HANDLER tags will be object references.
;
; KEYWORDS:
;
;       None.
;
;-
;*****************************************************************************************************
PRO IGL_BaseRangeObject::EventHandler, event

   @cat_pro_error_handler

   ; Get the name of the widget generating the event. Branch on this.
   event.ID -> GetProperty, Name=eventName
   CASE eventName OF


      'IGL_BASERANGEOBJECT PROPERTYSHEET': BEGIN

         IF event.type EQ 0 THEN BEGIN
            
            CASE StrUpCase(event.identifier) OF

               ELSE: BEGIN

                  component = event.component
                  identifier = event.identifier
                  event.id -> GetProperty, Value=value, Component=component, Property_Value=identifier
                  event.component -> SetPropertyByIdentifier, identifier, value

                  ;; Refresh graphic hierarchy
                  IF OBJ_VALDI(event.component) THEN IGL_RefreshDraw, event.component, /Buffered_Output

               END

            ENDCASE

         ENDIF ; of BUTTON DOWN EVENT

         END ; of IGL_BASERANGEOBJECT PROPERYSHEET events

        ; If you can't handle the event here. Pass it along to superclass EventHandler
        ELSE: self -> CatDataAtom::EventHandler, event

   ENDCASE

   ;; Report completion
   IF Obj_Valid(self) THEN self -> Report, /Completed

END

;*****************************************************************************************************
;+
; NAME:
;       IGL_BaseRangeObject::GETPROPERTY
;
; PURPOSE:
;
;       This method is used to get the object's properties.
;
; SYNTAX:
;
;       self -> GetProperty
;
; ARGUMENTS:
;
;       None.
;
; KEYWORDS:
;
;       XMIN:            The lower value of the x data range of the
;                              coordinate system associated with the object.
;
;       XMAX:           The upper value of the x data range of the
;                              coordinate system associated with the object.
;
;       YMIN:             The lower value of the y data range of the
;                              coordinate system associated with the object.
;
;       YMAX:           The upper value of the y data range of the
;                              coordinate system associated with the object.
;
;       XLOG:           The flag indicating if the x data range is logarithmic.
;
;       YLOG:           The flag indicating if the y data range is logarithmic.
;
;       STATIC:        The flag indicating if the coordinate system
;                              is static (data range can not be modified). 
;
;       _EXTRA:      Any keyword accepted by the CATDATAATOM::GetProperty method.
;-
;*****************************************************************************************************
PRO IGL_BaseRangeObject::GetProperty, XMIN=xmin, $
                                      XMAX=xmax, $
                                      YMIN=ymin, $
                                      YMAX=ymax, $
                                      XLOG=xlog, $
                                      YLOG=ylog, $
                                      STATIC=static, $
                                      DELETE=delete, $
                                      _REF_EXTRA=extra

   @cat_pro_error_handler

   IF OBJ_ISA_VALID(self._coords,'IGL_Coord') THEN BEGIN

      self._coords -> GetProperty, XRANGE=xrange, YRANGE=yrange, XLOG=xlog, YLOG=ylog, STATIC=static
      
      IF (arg_present(xmin)) THEN xmin = xrange[0]
      IF (arg_present(xmax)) THEN xmax = xrange[1]
      IF (arg_present(ymin)) THEN ymin = yrange[0]
      IF (arg_present(ymax)) THEN ymax = yrange[1]

   ENDIF

   IF (arg_Present(delete)) THEN delete = 0 ; Dummy value for REGISTERPROPERTY functionality.

   self -> CATDATAATOM::GetProperty, _extra=extra

   ;; Report completion
   self -> Report, /Completed

END

;*****************************************************************************************************
;+
; NAME:
;       IGL_BaseRangeObject::MOVE
;
; PURPOSE:
;
;       This method moves the object in the current window.
;       This an abstract method : derived classes should overload it
;        if the pan operation implemented here is not the desired move behaviour.
;
; SYNTAX:
;
;       self -> Move, x, y
;
; ARGUMENTS:
;
;       X:          The number of pixels to move in the X direction.
;
;       Y:          The number of pixels to move in the Y direction.
;
; KEYWORDS:
;
;       NODRAW:     If this keyword is set, only the coordinates are updated. No drawing occurs.
;
;       PIXMAP:     Set this keyword to a pixmap that can be used to erase the previous
;                   contents of the window.
;-
;*****************************************************************************************************
PRO IGL_BaseRangeObject::Move, x, y, PIXMAP=pixmap, NODRAW=noDraw
  
   @cat_pro_error_handler

   ;; Convert the device pixels into normal coordinates.
   self._coords -> GetProperty, position=pos
   c = Convert_Coord(x,y,/Device,/To_Normal)

   ;; Calculate new position values
   xmin = pos[0]+c[0,0]
   xmax = pos[2]+c[0,0]
   ymin = pos[1]+c[1,0]
   ymax = pos[3]+c[1,0]

   ;; Make sure that we have a valid normalized position array
   IF (xmin LT 0) OR (ymin LT 0) OR (xmax GT 1) OR (ymax GT 1) THEN RETURN
   IF (xmin GE xmax) THEN xmin = xmax-0.01
   IF (xmax LE xmin) THEN xmax = xmin+0.01
   IF (ymin GE ymax) THEN ymin = ymax-0.01
   IF (ymax LE ymin) THEN ymax = ymin+0.01

   ;; Update the object's coordinate system
   position = [xmin,ymin,xmax,ymax]
   self._coords -> SetProperty, position=position
 
   ;; Do you need to draw?
   IF ~Keyword_Set(nodraw) THEN BEGIN
      IF Obj_Isa_Valid(pixmap, 'PIXMAPWIDGET') THEN BEGIN
         self -> CopyParameters, pixmap, Destination=d, Extent=e
         pixmap -> Copy, Destination=d, Extent=e, Origin=d
         self -> Draw
      ENDIF ELSE BEGIN
         CatRefreshDraw, self, Stop_At='DRAWWIDGET', /NoErase
      ENDELSE
   ENDIF

   ;; Report completion
   self -> Report, /Completed
   
END


;*****************************************************************************************************
;+
; NAME:
;       IGL_BaseRangeObject::PAN
;
; PURPOSE:
;
;       This an abstract method. Derived classes should overload it
;       if the pan operation implemented here is not the desired pan behaviour.
;
; SYNTAX:
;
;       self -> Pan, x, y
;
; ARGUMENTS:
;
;       X:     The number of pixels to pan in the x direction.
;
;       Y:     The number of pixels to pan in the y direction.
;
; KEYWORDS:
;
;       None.
;-
;*****************************************************************************************************
PRO IGL_BaseRangeObject::Pan, x, y, PIXMAP=pixmap, NODRAW=nodraw 

   @cat_pro_error_handler

  ;; Make sure we have a valid coordinate system
  IF (obj_isa_valid(self._coords,'IGL_Coord') EQ 0) THEN RETURN

  ;; No pan if the object is static
  self._coords -> GetProperty, STATIC=IsStatic
  IF IsStatic THEN RETURN

  ;; Apply the coordinate system
  self -> ApplyCoords

  ;; Convert pixel to data coordinates
  self._coords -> GetProperty, xrange=xr, yrange=yr, position=pos, xlog=xlog, ylog=ylog
  IF (xlog) THEN BEGIN
     xx = (alog10(xr[0]) - alog10(xr[1])) / (!D.X_Size * Abs(pos[2]-pos[0])) * x
     xrange = 10.0^(alog10(xr)+xx)
  ENDIF ELSE BEGIN
     xx = (xr[0]- xr[1]) / (!D.X_Size * Abs(pos[2]-pos[0])) * x
     xrange = xr+xx
  ENDELSE
  IF (ylog) THEN BEGIN
     yy = (alog10(yr[0]) - alog10(yr[1])) / (!D.Y_Size * Abs(pos[3]-pos[1])) * y
     yrange = 10.0^(alog10(yr)+yy)
  ENDIF ELSE BEGIN
     yy = (yr[0]- yr[1]) / (!D.Y_Size * Abs(pos[3]-pos[1])) * y
     yrange = yr+yy
  ENDELSE

  ;; Add the x and y increment to the xrange and yrange
  self._coords -> SetProperty, xrange=xrange, yrange=yrange

  ;; Do you need to draw?
  IF ~Keyword_Set(nodraw) THEN BEGIN
     IF Obj_Isa_Valid(pixmap, 'PIXMAPWIDGET') THEN BEGIN
        self -> CopyParameters, pixmap, Destination=d, Extent=e
        pixmap -> Copy, Destination=d, Extent=e, Origin=d
        self -> Draw
     ENDIF ELSE BEGIN
        CatRefreshDraw, self, Stop_At='DRAWWIDGET', /NoErase
     ENDELSE
  ENDIF

  ;; Report completion
  self -> Report, /Completed 

END


;*****************************************************************************************************
;+
; NAME:
;       IGL_BaseRangeObject::SETPROPERTY
;
; PURPOSE:
;
;       This method is used to set the object's properties.
;
; SYNTAX:
;
;       self -> SetProperty
;
; ARGUMENTS:
;
;       None.
;
; KEYWORDS:
;
;       XMIN:            The lower value of the x data range of the
;                              coordinate system associated with the object.
;
;       XMAX:           The upper value of the x data range of the
;                              coordinate system associated with the object.
;
;       YMIN:             The lower value of the y data range of the
;                              coordinate system associated with the object.
;
;       YMAX:           The upper value of the y data range of the
;                              coordinate system associated with the object.
;
;       XLOG:           The flag indicating if the x data range is logarithmic.
;
;       YLOG:           The flag indicating if the y data range is logarithmic.
;
;       STATIC:        The flag indicating if the coordinate system
;                              is static (data range can not be modified). 
;
;       DELETE:      Set this keyword to destroy the object.
;                              An 'OBJECT_DELETED' message is sent.
;
;       _EXTRA:     Any keyword accepted by the CATDATAATOM::SetProperty method.
;
;-
;*****************************************************************************************************
PRO IGL_BaseRangeObject::SetProperty, XMIN=xmin, $
                                      XMAX=xmax, $
                                      YMIN=ymin, $
                                      YMAX=ymax, $
                                      XLOG=xlog, $
                                      YLOG=ylog, $
                                      STATIC=static, $
                                      DELETE=delete, $
                                      _EXTRA=extra
  
   @cat_pro_error_handler

   self -> CATDATAATOM::SetProperty, _extra=extra

   IF OBJ_ISA_VALID(self._coords,'IGL_Coord') THEN BEGIN
      
      self._coords -> GetProperty, XRANGE=xr, YRANGE=yr
      IF N_ELEMENTS(xmin) NE 0 THEN xrange = [xmin,xr[1]]
      IF N_ELEMENTS(xmax) NE 0 THEN xrange = [xr[0],xmax]
      IF N_ELEMENTS(ymin) NE 0 THEN yrange = [ymin,yr[1]]
      IF N_ELEMENTS(ymax) NE 0 THEN yrange = [yr[0],ymax]        
      self._coords -> SetProperty, XRANGE=xrange, YRANGE=yrange, XLOG=xlog, YLOG=ylog, STATIC=static
      
   ENDIF
   
   IF (keyword_set(delete)) THEN BEGIN
      self -> SetProperty, Visible=0
      IGL_RefreshDraw, self, /Buffered_Output
      Obj_Destroy, self._controlPanel
      self -> GetProperty, Parent=parents
      self -> SendMessage, 'OBJECT_DELETED', DATA=self
      FOR j=0,N_Elements(parents)-1 DO IF OBJ_VALID(parents[j]) THEN parents[j] -> Remove, self
      Obj_Destroy, self
      RETURN
   ENDIF
   
   ;; Report completion
   IF OBJ_VALID(self) THEN self -> Report, /Completed

END


;*****************************************************************************************************
;+
; NAME:
;       IGL_BaseRangeObject::ZOOMIN
;
; PURPOSE:
;
;       This an abstract method. Derived classes should overload it
;       if the zoom operation implemented here is not the desired zoom behaviour.
;
; SYNTAX:
;
;       self -> ZoomIn, x, y
;
; ARGUMENTS:
;
;       X:     The x location of the cursor where to zoom in.
;
;       Y:     The y location of the cursor where to zoom in.
;
; KEYWORDS:
;
;       None.
;-
;*****************************************************************************************************
PRO IGL_BaseRangeObject::ZoomIn, x, y, PIXMAP=pixmap, NODRAW=nodraw

  @cat_pro_error_handler

  ;; Make sure we have a valid coordinate system
  IF (obj_isa_valid(self._coords,'IGL_Coord') EQ 0) THEN RETURN

  ;; No zoom if the object is static
  self._coords -> GetProperty, STATIC=IsStatic
  IF IsStatic THEN RETURN

  ;; Apply the coordinate system
  self -> ApplyCoords

  ;; Convert pixel to data coordinates
  self._coords -> GetProperty, xrange=xr, yrange=yr, xlog=xlog, ylog=ylog
  cc = convert_coord([x,y],/device,/to_data)
  xloc = cc[0]
  yloc = cc[1]

  ;; Make the new range around the clicked location
  ;; 70% less than the previous range
  IF (xlog) THEN BEGIN
     dx = 0.70*(alog10(xr[1])-alog10(xr[0]))
     x0 = 10.0^(alog10(xloc)-0.5*dx)
     x1 = 10.0^(alog10(xloc)+0.5*dx)
  ENDIF ELSE BEGIN
     dx = 0.70*(xr[1]-xr[0])
     x0 = xloc-0.5*dx
     x1 = xloc+0.5*dx
  ENDELSE

  IF (ylog) THEN BEGIN
     dy = 0.70*(alog10(yr[1])-alog10(yr[0]))
     y0 = 10.0^(alog10(yloc)-0.5*dy)
     y1 = 10.0^(alog10(yloc)+0.5*dy)
  ENDIF ELSE BEGIN
     dy = 0.70*(yr[1]-yr[0])
     y0 = yloc-0.5*dy
     y1 = yloc+0.5*dy
  ENDELSE

  ;; Assign new x and yrange
  xrange = [x0,x1]
  yrange = [y0,y1]
  self._coords -> SetProperty, xrange=xrange, yrange=yrange

  ;; Do you need to draw?
  IF ~Keyword_Set(nodraw) THEN BEGIN
     IF Obj_Isa_Valid(pixmap, 'PIXMAPWIDGET') THEN BEGIN
        self -> CopyParameters, pixmap, Destination=d, Extent=e
        pixmap -> Copy, Destination=d, Extent=e, Origin=d
        self -> Draw
     ENDIF ELSE BEGIN
        CatRefreshDraw, self, Stop_At='DRAWWIDGET', /NoErase
     ENDELSE
  ENDIF

   ;; Report completion
  self -> Report, /Completed

END


;*****************************************************************************************************
;+
; NAME:
;       IGL_BaseRangeObject::ZOOMOUT
;
; PURPOSE:
;
;       This an abstract method. Derived classes should overload it
;       if the zoom operation implemented here is not the desired zoom behaviour.
;
; SYNTAX:
;
;       self -> ZoomOut, x, y
;
; ARGUMENTS:
;
;       X:     The x location of the cursor where to zoom out.
;
;       Y:     The y location of the cursor where to zoom out.
;
; KEYWORDS:
;
;       ALLTHEWAY:  A keyword to indicate that a complete zoom out
;                   operation is needed.
;-
;*****************************************************************************************************
PRO IGL_BaseRangeObject::ZoomOut, x, y, ALLTHEWAY=alltheway, PIXMAP=pixmap, NODRAW=nodraw

  @cat_pro_error_handler

  ;; Make sure we have a valid coordinate system
  IF (obj_isa_valid(self._coords,'IGL_Coord') EQ 0) THEN RETURN

  ;; No zoom if the object is static
  self._coords -> GetProperty, STATIC=IsStatic
  IF IsStatic THEN RETURN

  ;; Apply the coordinate system
  self -> ApplyCoords

  ;; Convert pixel to data coordinates
  self._coords -> GetProperty, xrange=xr, yrange=yr, xlog=xlog, ylog=ylog
  cc = convert_coord([x,y],/device,/to_data)
  xloc = cc[0]
  yloc = cc[1]

  ;; Make the new range around the clicked location
  ;; ~140% more than the previous range
  IF (xlog) THEN BEGIN
     dx = 1/0.70*(alog10(xr[1])-alog10(xr[0]))
     x0 = 10.0^(alog10(xloc)-0.5*dx)
     x1 = 10.0^(alog10(xloc)+0.5*dx)
  ENDIF ELSE BEGIN
     dx = 1/0.70*(xr[1]-xr[0])
     x0 = xloc-0.5*dx
     x1 = xloc+0.5*dx
  ENDELSE

  IF (ylog) THEN BEGIN
     dy = 1/0.70*(alog10(yr[1])-alog10(yr[0]))
     y0 = 10.0^(alog10(yloc)-0.5*dy)
     y1 = 10.0^(alog10(yloc)+0.5*dy)
  ENDIF ELSE BEGIN
     dy = 1/0.70*(yr[1]-yr[0])
     y0 = yloc-0.5*dy
     y1 = yloc+0.5*dy
  ENDELSE

  ;; Assign new x and yrange
  xrange = [x0,x1]
  yrange = [y0,y1]
  self._coords -> SetProperty, xrange=xrange, yrange=yrange

  ;; Do you need to draw?
  IF ~Keyword_Set(nodraw) THEN BEGIN
     IF Obj_Isa_Valid(pixmap, 'PIXMAPWIDGET') THEN BEGIN
        self -> CopyParameters, pixmap, Destination=d, Extent=e
        pixmap -> Copy, Destination=d, Extent=e, Origin=d
        self -> Draw
     ENDIF ELSE BEGIN
        CatRefreshDraw, self, Stop_At='DRAWWIDGET', /NoErase
     ENDELSE
  ENDIF

   ;; Report completion
  self -> Report, /Completed

END


;*****************************************************************************************************
;+
; NAME:
;       IGL_BaseRangeObject::CLEANUP
;
; PURPOSE:
;
;       This is the IGL_BaseRangeObject object class destructor method.
;
; SYNTAX:
;
;       Called automatically when the object is destroyed.
;
; ARGUMENTS:
;
;       None.
;
; KEYWORDS:
;
;       None.
;-
;*****************************************************************************************************
PRO IGL_BaseRangeObject::CLEANUP

   @cat_pro_error_handler

   self -> CATDATAATOM::Cleanup

   ;; Report completion
   self -> Report, /Completed
   
END


;*****************************************************************************************************
;+
; NAME:
;       IGL_BaseRangeObject::INIT
;
; PURPOSE:
;
;       This is the IGL_BaseRangeObject object class creator method.
;
; SYNTAX:
;
;       Called automatically when the object is created.
;
; ARGUMENTS:
;
;       None.
;
; KEYWORDS:
;
;       COORD_OBJECT:  A reference to a IGL_Coord object which becomes
;                                          the coordinate system for this object.
;
;       _EXTRA:                   Any keyword accepted by the CATDATAATOM::Init method.
;
;-
;*****************************************************************************************************
FUNCTION IGL_BaseRangeObject::INIT, COORD_OBJECT=coord_object, _EXTRA=extra

   @cat_func_error_handler

   IF N_ELEMENTS(coord_object) EQ 0 THEN coord_object = obj_new('IGL_Coord')

   ;; Initialize the CATDATAATOM superclass
   ok = self -> CATDATAATOM::Init(COORD_OBJECT=coord_object,_EXTRA=extra)

   IF (NOT ok) THEN RETURN, 0

   self -> RegisterProperty, 'DELETE', /boolean, name='Delete Object'
   self -> RegisterProperty, 'STATIC', /boolean, name='Static Data range'
   self -> RegisterProperty, 'XMIN', /float, name='Minimum X range'
   self -> RegisterProperty, 'XMAX', /float, name='Maximum X range'
   self -> RegisterProperty, 'YMIN', /float, name='Minimum Y range'
   self -> RegisterProperty, 'YMAX', /float, name='Maximum Y range'
   self -> RegisterProperty, 'XLOG', /boolean, name='Logarithmic X range'
   self -> RegisterProperty, 'YLOG', /boolean, name='Logarithmic Y range'

   ;; Report completion
   self -> Report, /Completed

   RETURN, 1

END

;*****************************************************************************************************
;
; NAME:
;       IGL_BASERANGEOBJECT CLASS DEFINITION
;
; PURPOSE:
;
;       This is the IGL_BaseRangeObject object's structure definition code.
;
;*****************************************************************************************************
PRO IGL_BaseRangeObject__DEFINE, class

  COMPILE_OPT idl2, HIDDEN

  class = {IGL_BaseRangeObject, $
           INHERITS CATDATAATOM $ ; This is a CATDATAATOM object
          }

  struct_hide, class

END
